<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Generator</title>
</head>
<body>
<script type="text/javascript">
    {
        /**
         * Generator函数是ES6提供的一种异步编程解决方案
         */
        function* helloWorldGenerator() {
            yield 'hello';
            yield 'world';
            return 'ending';
        }

        let hw = helloWorldGenerator();
        /*上面代码定义了一个Generator函数helloWorldGenerator，它内部有两个yield语句“hello”和“world”，即该函数有三个状态：hello，world和return语句(结束执行)
        * 然后，Generator函数的调用方法与普通函数一样，也是在函数名后面加上一对圆括号。
        * 不同的是，调用Generator函数后，该函数并不执行，返回的也不是函数运行结果，而是一个指向内部状态的指针对象，也就是上一章介绍的遍历器对象(Iterator Object)
        * 下一步，必须调用遍历器对象的next方法，使得指针移向下一个状态。
        * 也就是说，每次调用next方法，内部指针就从函数头部或上一次停下来的地方开始执行，直到遇到下一个yield语句(或return语句)为止。
        * 换言之，Generator函数是分段执行的，yield语句是暂停执行的标记，而next方法可以恢复执行*/

        console.log(hw.next()); // {value: "hello", done: false}
        console.log(hw.next()); // {value: "world", done: false}
        console.log(hw.next()); // {value: "ending", done: true}
        console.log(hw.next()); // {value: undefined, done: true}

        /*总结一下，调用Generator函数，返回一个遍历器对象，代表Generator函数的内部指针。
        以后，每次调用遍历器对象的next方法，就会返回一个有着value和done两个属性的对象。
        value属性表示当前的内部状态的值，是yield语句后面那个表达式的值；done属性是一个布尔值，表示是否遍历结束*/
    }
    {
        /**
         * yield语句
         * 由于Generator函数返回的遍历器对象，只有调用next方法才会遍历下一个内部状态，所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志
         */

        /*遍历器对象的next方法的运行逻辑如下
        * 1、遇到yield语句，就暂停执行后面的操作，并将紧跟在yield后面的那个表达式的值，作为返回的对象的value属性值
        * 2、下一次调用next方法时，再继续往下执行，直到遇到下一个yield语句
        * 3、如果没有再遇到新的yield语句，就一直运行到函数结束，直到return语句为止，并将return语句后面的表达式的值，作为返回的对象的value属性值
        * 4、如果该函数没有return语句，则返回的对象的value属性值为undefined。
        * 需要注意的是，yield语句后面的表达式，只有当调用next方法、内部指针指向该语句时才会执行，因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能*/
        function* gen() {
            yield  123 + 456;
        }

        let sum = gen();
        console.log(sum.next()); // {value: 579, done: false}
        console.log(sum.next()); // {value: undefined, done: true}
        /*上面代码中，yield后面的表达式123 + 456，不会立即求值，只会在next方法将指针移到这一句时，才会求值*/

        /*yield语句与return语句既有相似之处，也有区别
        * 相似之处：都能返回紧跟在语句后面的那个表达式的值
        * 区别：每次遇到yield，函数暂停执行，下一次再从该位置继续向后执行，而return语句不具备位置记忆的功能
        * 一个函数里面，只能执行一次(或者说一个)return语句，但是可以执行多次(或者说多个)yield语句。
        * 正常函数只能返回一个值，因为只能执行一次return
        * Generator函数可以返回一系列的值，因为可以有任意多个yield*/

        /*Generator函数可以不用yield语句，这时就变成了一个单纯的暂缓执行函数*/
        function* f() {
            console.log('执行了！')
        }

        const generator = f();

        setTimeout(function () {
            generator.next(); // 执行了！
        }, 2000);
        /*上面代码中，函数f如果是普通函数，在为变量generator赋值时就会执行。但是，函数f是一个Generator函数，就变成只有调用next方法时，函数f才会执行*/

        /*yield语句不能用在普通函数中，否则会报错*/
        /*(function (){
            yield 1;
        })()*/
        /*const arr = [1, [[2, 3], 4], [5, 6]];
        const flat = function* (a) {
            a.forEach(function (item) {
                if (typeof item !== 'number') {
                    yield * flat(item);
                } else {
                    yield item;
                }
            }
        };
        for (const f of flat(arr)){
            console.log(f);
        }*/

        const arr = [1, [[2, 3], 4], [5, 6]];

        const flat = function* (a) {
            const length = a.length;
            for (let i = 0; i < length; i++) {
                const item = a[i];
                if (typeof item !== 'number') {
                    yield* flat(item);
                } else {
                    yield item;
                }
            }
        };

        for (const f of flat(arr)) {
            console.log(f); // 1 2 3 4 5 6
        }

        /*yield语句如果用在一个表达式之中，必须放在圆括号里面*/
        // console.log('Hello' + yield); // SyntaxError
        // console.log('Hello' + yield 123); // SyntaxError

        // console.log('Hello' + (yield));
        // console.log('Hello' + (yield 123));
    }
    {
        /**
         * 与Iterator接口的关系
         * 由于Generator函数就是遍历器生成函数，因此可以把Generator赋值给对象的Symbol.iterator属性，从而使得该对象具有Iterator接口
         */
        const myIterable = {};
        myIterable[Symbol.iterator] = function* () {
            yield 1;
            yield 2;
            yield 3;
        };
        console.log([...myIterable]); // [1, 2, 3]
        /*上面代码中，Generator函数赋值给Symbol.iterator属性，从而使得myIterable对象具有了Iterator接口，可以被...运算符遍历了*/

        function* gen() {
            // some code
            console.log(2333);
        }

        let voids = gen();
        console.log(voids[Symbol.iterator]() === voids); // true
        /*上面代码中，gen是一个Generator函数，调用它会生成一个遍历器对象g。它的Symbol.iterator属性，也是一个遍历器对象生成函数，执行后返回它自己*/
    }
    {
        /**next方法的参数*/

        /*yield句本身没有返回值，或者说总是返回undefined。next方法可以带一个参数，该参数就会被当作上一个yield语句的返回值*/
        function* f() {
            for (let i = 0; true; i++) {
                const reset = yield i;
                if (reset) {
                    i = -1;
                }
            }
        }

        let f1 = f();
        console.log(f1.next()); // {value: 0, done: false}
        console.log(f1.next()); // {value: 1, done: false}
        console.log(f1.next(true)); // {value: 0, done: false}
        /*上面代码先定义了一个可以无限运行的Generator函数f，如果next方法没有参数，每次运行到yield语句，变量reset的值总是undefined。
        当next方法带一个参数true时，当前的变量reset就被重置为这个参数(即true)，因此i会等于-1，下一轮循环就会从-1开始递增*/

        function* foo(x) {
            const y = 2 * (yield(x + 1));
            const z = yield(y / 3);
            return (x + y + z);
        }

        const a = foo(5);
        console.log(a.next()); // {value: 6, done: false}
        console.log(a.next()); // {value: NaN, done: false}
        console.log(a.next()); // {value: NaN, done: true}
        /*上面代码中，第二次运行next方法的时候不带参数，导致y的值等于2 * undefined(即NaN)，除以3以后还是NaN，因此返回对象的value属性也等于NaN。
        第三次运行Next方法的时候不带参数，所以z等于undefined，返回对象的value属性等于5 + NaN + undefined，即NaN*/

        const b = foo(5);
        console.log(b.next()); // {value: 6, done: false}
        console.log(b.next(12)); // {value: 8, done: false}
        console.log(b.next(13)); // {value: 42, done: true}
        /*如果向next方法提供参数，返回结果就完全不一样了。
        上面代码第一次调用b的next方法时，返回x+1的值6；
        第二次调用next方法，将上一次yield语句的值设为12，因此y等于24，返回y / 3的值8；
        第三次调用next方法，将上一次yield语句的值设为13，因此z等于13，这时x等于5，y等于24，所以return语句的值等于42*/

        /*如果想要第一次调用next方法时，就能够输入值，可以在Generator函数外面再包一层*/
        function wrapper(generatorFunction) {
            return function (...args) {
                let generatorObject = generatorFunction(...args);
                generatorObject.next();
                return generatorObject;
            };
        }

        const wrapped = wrapper(function* () {
            console.log(`First input: ${yield}`);
            return 'DONE';
        });
        console.log(wrapped().next('hello!')); // First input: hello!  {value: "DONE", done: true}

        /*上面代码中，Generator函数如果不用wrapper先包一层，是无法第一次调用next方法，就输入参数的*/

        /*再看一个通过next方法的参数，向Generator函数内部输入值的例子*/
        function* dataConsumer() {
            console.log('Started');
            console.log(`1. ${yield}`);
            console.log(`2. ${yield}`);
            return 'result';
        }

        let genObj = dataConsumer();
        console.log(genObj.next()); // Started  {value: undefined, done: false}
        console.log(genObj.next('a')); // 1. a  {value: undefined, done: false}
        console.log(genObj.next('b')); // 2. b  {value: "result", done: true}
        /*上面代码是一个很直观的例子，每次通过next方法向Generator函数输入值，然后打印出来*/
    }
    {
        /**
         * for...of循环：自动遍历Generator函数时生成的Iterator对象，且此时不再需要调用next方法
         */
        function* foo() {
            yield 1;
            yield 2;
            yield 3;
            yield 4;
            yield 5;
            return 6;
        }

        for (let v of foo()) {
            console.log(v); // 1 2 3 4 5
        }
        /*上面代码使用for...of循环，依次显示5个yield语句的值。
        这里需要注意，一旦next方法的返回对象的done属性为true，for...of循环就会中止，且不包含该返回对象，所以上面代码的return语句返回的6，不包括在for...of循环之中*/

        /*下面是一个利用Generator函数和for...of循环，实现斐波那契数列的例子*/
        function* fibonacci() {
            let [prev, curr] = [0, 1];
            for (; ;) {
                [prev, curr] = [curr, prev + curr];
                yield curr;
            }
        }

        for (let n of fibonacci()) {
            if (n > 1000) break;
            console.log(n); // 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
        }

        /*从上面代码可见，使用for...of语句时不需要使用next方法
        * 利用for...of循环，可以写出遍历任意对象(object)的方法。
        * 原生的JavaScript对象没有遍历接口，无法使用for...of循环，通过Generator函数为它加上这个接口，就可以用了*/
        function* objectEntries(obj) {
            let propKeys = Reflect.ownKeys(obj);

            for (let propKey of propKeys) {
                yield [propKey, obj[propKey]];
            }
        }

        let jane = {first: 'Jane', last: 'Doe'};
        for (let [key, value] of objectEntries(jane)) {
            console.log(`${key}: ${value}`); // first: Jane  last: Doe
        }

        /*上面代码中，对象jane原生不具备Iterator接口，无法用for...of遍历。
        这时，我们通过Generator函数objectEntries为它加上遍历器接口，就可以用for...of遍历了。
        加上遍历器接口的另一种写法是，将Generator函数加到对象的Symbol.iterator属性上面*/

        function* objectEntries2() {
            let propKeys = Object.keys(this);

            for (let propKey of propKeys) {
                yield [propKey, this[propKey]];
            }
        }

        jane[Symbol.iterator] = objectEntries2;
        for (let [key, value] of jane) {
            console.log(`${key}: ${value}`); // first: Jane  last: Doe
        }

        /*除了for...of循环以外，扩展运算符(...)、解构赋值和Array.from方法内部调用的，都是遍历器接口。
        这意味着，它们都可以将Generator函数返回的Iterator对象，作为参数*/
        function* numbers() {
            yield 1;
            yield 2;
            return 3;
            yield 4;
        }

        /*扩展运算符*/
        console.log([...numbers()]); // [1, 2]

        /*Array.from 方法*/
        console.log(Array.from(numbers())); // [1, 2]

        /*解构赋值*/
        let [x, y] = numbers();
        console.log(x); // 1
        console.log(y); // 2

        /*for...of 循环*/
        for (let n of numbers()) {
            console.log(n); // 1 2
        }
    }
    {
        /**
         * Generator.prototype.throw()
         * Generator函数返回的遍历器对象，都有一个throw方法，可以在函数体外抛出错误，然后在Generator函数体内捕获
         */
        const g = function* () {
            try {
                yield;
            } catch (e) {
                console.log('内部捕获', e);
            }
        };
        const i = g();
        console.log(i.next()); // {value: undefined, done: false}

        try {
            i.throw('a');
            i.throw('b');
        } catch (e) {
            console.log('外部捕获', e);
        }
        // 内部捕获 a  内部捕获 b
        /*上面代码中，遍历器对象i连续抛出两个错误。第一个错误被Generator函数体内的catch语句捕获。
        i第二次抛出错误，由于Generator函数内部的catch语句已经执行过了，不会再捕捉到这个错误了，所以这个错误就被抛出了Generator函数体，被函数体外的catch语句捕获。*/

        /*throw方法可以接受一个参数，该参数会被catch语句接收，建议抛出Error对象的实例*/
        const g1 = function* () {
            try {
                yield;
            } catch (e) {
                console.log(e);
            }
        };
        const j = g1();
        console.log(j.next()); // {value: undefined, done: true}
        console.log(j.throw(new Error('出错了！'))); // Error: 出错了！
        /*注意，不要混淆遍历器对象的throw方法和全局的throw命令。上面代码的错误，是用遍历器对象的throw方法抛出的，而不是用throw命令抛出的。后者只能被函数体外的catch语句捕获*/

        const g2 = function* () {
            while (true) {
                try {
                    yield;
                } catch (e) {
                    if (e !== 'a') throw e;
                    console.log('内部捕获', e);
                }
            }
        };
        const x = g2();
        console.log(x.next()); // {value: undefined, done: false}
        try {
            throw new Error('a');
            throw new Error('b');
        } catch (e) {
            console.log('外部捕获', e); // 外部捕获 Error: a
        }
        /*上面代码之所以只捕获了a，是因为函数体外的catch语句块，捕获了抛出的a错误以后，就不会再继续try代码块里面剩余的语句了。*/

        /*如果Generator函数内部没有部署try...catch代码块，那么throw方法抛出的错误，将被外部try...catch代码块捕获*/
        const g3 = function* () {
            while (true) {
                yield;
                console.log('内部捕获', e);
            }
        };
        const y = g3();
        console.log(y.next());
        try {
            i.throw('a');
            i.throw('b');
        } catch (e) {
            console.log('外部捕获', e); // 外部捕获 a
        }
        /*上面代码中，Generator函数g内部没有部署try...catch代码块，所以抛出的错误直接被外部catch代码块捕获*/

        /*如果Generator函数内部和外部，都没有部署try...catch代码块，那么程序将报错，直接中断执行*/
        const gen = function* gen() {
            // yield console.log('hello');
            // yield console.log('world');
        };
        const g4 = gen();
        g4.next(); // hello
        // g4.throw(); // Uncaught undefined
        /*上面代码中，g.throw抛出错误以后，没有任何try...catch代码块可以捕获这个错误，导致程序报错，中断执行*/

        /*throw方法被捕获以后，会附带执行下一条yield语句。也就是说，会附带执行一次next方法*/
        const gen1 = function* gen() {
            try {
                yield console.log('a');
            } catch (e) {
                // ...
            }
            yield console.log('b');
            yield console.log('c');
        };
        let gen2 = gen1();
        gen2.next(); // a
        gen2.throw(); // b
        gen2.next(); // c
        /*上面代码中，g.throw方法被捕获以后，自动执行了一次next方法，所以会打印b。
        另外，也可以看到，只要Generator函数内部部署了try...catch代码块，那么遍历器的throw方法抛出的错误，不影响下一次遍历*/

        /*这种函数体内捕获错误的机制，大大方便了对错误的处理。多个yield语句，可以只用一个try...catch代码块来捕获错误。
        如果使用回调函数的写法，想要捕获多个错误，就不得不为每个函数内部写一个错误处理语句，现在只在Generator函数内部写一次catch语句就可以了*/

        /*Generator函数体外抛出的错误，可以在函数体内捕获；反过来，Generator函数体内抛出的错误，也可以被函数体外的catch捕获*/
        function* foo() {
            const x = yield 3;
            const y = x.toUpperCase();
            yield y;
        }

        const it = foo();
        console.log(it.next()); // {value: 3, done: false}
        try {
            it.next(42);
        } catch (err) {
            console.log(err); // TypeError: x.toUpperCase is not a function
        }

        /*上面代码中，第二个next方法向函数体内传入一个参数42，数值是没有toUpperCase方法的，所以会抛出一个TypeError错误，被函数体外的catch捕获*/

        function* g5() {
            yield 1;
            console.log('throwing an exception');
            throw new Error('generator broke!');
            yield 2;
            yield 3;
        }

        function log(generator) {
            let v;
            console.log('starting generator');
            try {
                v = generator.next();
                console.log('第一次运行next方法', v);
            } catch (err) {
                console.log('捕捉错误', v);
            }
            try {
                v = generator.next();
                console.log('第二次运行next方法', v);
            } catch (err) {
                console.log('捕捉错误', v);
            }
            try {
                v = generator.next();
                console.log('第三次运行next方法', v);
            } catch (err) {
                console.log('捕捉错误', v);
            }
            console.log('caller done');
        }

        log(g5());
        // starting generator
        // 第一次运行next方法 {value: 1, done: false}
        // throwing an exception
        // 捕捉错误 {value: 1, done: false}
        // 第三次运行next方法 {value: undefined, done: true}
        // caller done
        /*上面代码一共三次运行next方法，第二次运行的时候会抛出错误，然后第三次运行的时候，Generator函数就已经结束了，不再执行下去了*/
    }
    {
        /**
         * Generator.prototype.return()
         * Generator函数返回的遍历器对象，还有一个return方法，可以返回给定的值，并且终结遍历Generator函数
         */
        function* gen() {
            yield 1;
            yield 2;
            yield 3;
        }

        const g = gen();
        console.log(g.next()); // {value: 1, done: false}
        console.log(g.return('foo')); // {value: "foo", done: true}
        console.log(g.next()); // {value: undefined, done: true}

        /*上面代码中，遍历器对象g调用return方法后，返回值的value属性就是return方法的参数foo。
        并且，Generator函数的遍历就终止了，返回值的done属性为true，以后再调用next方法，done属性总是返回true*/

        /*如果return方法调用时，不提供参数，则返回值的value属性为undefined*/
        const g1 = gen();
        console.log(g1.next()); // {value: 1, done: false}
        console.log(g1.return()); // {value: undefined, done: true}

        /*如果Generator函数内部有try...finally代码块，那么return方法会推迟到finally代码块执行完再执行*/
        function* numbers() {
            yield 1;
            try {
                yield 2;
                yield 3;
            } finally {
                yield 4;
                yield 5;
            }
            yield 6;
        }

        let num = numbers();
        console.log(num.next()); // {value: 1, done: false}
        console.log(num.next()); // {value: 2, done: false}
        console.log(num.return(7)); // {value: 4, done: false}
        console.log(num.next()); // {value: 5, done: false}
        console.log(num.next()); // {value: 7, done: true}
        /*上面代码中，调用return方法后，就开始执行finally代码块，然后等到finally代码块执行完，再执行return方法*/
    }
    {
        /**
         * yield*语句
         * 如果在Generator函数内部，调用另一个Generator函数，默认情况下是没有效果的
         */
        function* foo() {
            yield 'a';
            yield 'b';
        }

        function* bar() {
            yield 'x';
            foo();
            yield 'y';
        }

        for (let v of bar()) {
            console.log(v); // x y
        }
        /*上面代码中，foo和bar都是Generator函数，在bar里面调用foo，是不会有效果的*/

        /*这个就需要用到yield*语句，用来在一个Generator函数里面执行另一个Generator函数*/
        function* bar2() {
            yield 'x';
            yield* foo();
            yield 'y';
        }

        /*等同于*/
        /*function* bar2() {
            yield 'x';
            yield 'a';
            yield 'b';
            yield 'y';
        }*/

        /*等同于*/
        /*function* bar2() {
            yield 'x';
            for (let v of foo()) {
                yield v;
            }
            yield 'y';
        }*/

        for (let v of bar2()) {
            console.log(v); // x a b y
        }

        /*再来看一个对比的例子*/
        function* inner() {
            yield 'hello!';
        }

        function* outer1() {
            yield 'open';
            yield inner();
            yield 'close';
        }

        const gen = outer1();
        console.log(gen.next().value); // open
        console.log(gen.next().value); // 返回一个遍历器对象 inner {<suspended>}
        console.log(gen.next().value); // close

        function* outer2() {
            yield 'open';
            yield* inner();
            yield 'close';
        }

        const gen1 = outer2();
        console.log(gen1.next().value); // open
        console.log(gen1.next().value); // hello!
        console.log(gen1.next().value); // close
        /*上面例子中，outer2使用了yield*，outer1没使用。结果就是，outer1返回一个遍历器对象，outer2返回该遍历器对象的内部值
        * 从语法角度看，如果yield命令后面跟的是一个遍历器对象，需要在yield命令后面加上星号，表明它返回的是一个遍历器对象。这被称为yield*语句*/

        let delegatedIterator = (function* () {
            yield 'Hello!';
            yield 'Bye!';
        }());

        let delegatingIterator = (function* () {
            yield 'Greetings!';
            yield* delegatedIterator;
            yield 'Ok, bye.';
        }());

        for (let value of delegatingIterator) {
            console.log(value); // Greetings! Hello! Bye! Ok, bye.
        }
        /*上面代码中，delegatingIterator是代理者，delegatedIterator是被代理者。
        由于yield* delegatedIterator语句得到的值，是一个遍历器，所以要用星号表示。
        运行结果就是使用一个遍历器，遍历了多个Generator函数，有递归的效果*/

        /*yield*后面的Generator函数(没有return语句时)，等同于在Generator函数内部，部署一个for...of循环*/
        function* concat(iter1, iter2) {
            yield* iter1;
            yield* iter2;
        }

        /*等同于*/
        /*function* concat(iter1, iter2) {
            let value;
            for (value of iter1) {
                yield value;
            }
            for (value of iter2) {
                yield value;
            }
        }*/

        /*如果yield*后面跟着一个数组，由于数组原生支持遍历器，因此就会遍历数组成员*/
        function* generator() {
            yield* ["a", "b", "c"];
        }

        console.log(generator().next()); // {value: "a", done: false}
        console.log(generator().next()); // {value: "a", done: false}
        console.log(generator().next()); // {value: "a", done: false}
        console.log(generator().next()); // {value: "a", done: false}
        /*上面代码中，yield命令后面如果不加星号，返回的是整个数组，加了星号就表示返回的是数组的遍历器对象*/

        /*实际上，任何数据结构只要有Iterator接口，就可以被yield*遍历*/
        let read = (function* () {
            yield 'hello';
            yield* 'hello';
        })();
        console.log(read.next().value); // hello
        console.log(read.next().value); // h
        console.log(read.next().value); // e
        /*上面代码中，yield语句返回整个字符串，yield*语句返回单个字符。因为字符串具有Iterator接口，所以被yield*遍历*/

        /*如果被代理的Generator函数有return语句，那么就可以向代理它的Generator函数返回数据*/
        function* foo1() {
            yield 2;
            yield 3;
            return "foo";
        }

        function* bar1() {
            yield 1;
            const v = yield* foo1();
            console.log("v: " + v);
            yield 4;
        }

        const it = bar1();
        console.log(it.next()); // {value: 1, done: false}
        console.log(it.next()); // {value: 2, done: false}
        console.log(it.next()); // {value: 3, done: false}
        console.log(it.next()); // v: foo  {value: 4, done: false}
        console.log(it.next()); // {value: undefined, done: true}
        /*上面代码在第四次调用next方法的时候，屏幕上会有输出，这是因为函数foo的return语句，向函数bar提供了返回值*/

        /*再看一个例子*/
        function* genFuncWithReturn() {
            yield 'a';
            yield 'b';
            return 'The result';
        }

        function* logReturned(genObj) {
            let result = yield* genObj;
            console.log(result);
        }

        console.log([...logReturned(genFuncWithReturn())]); // The result   ["a", "b"]
        /*上面代码中，存在两次遍历。
        第一次是扩展运算符遍历函数logReturned返回的遍历器对象，第二次是yield*语句遍历函数genFuncWithReturn返回的遍历器对象。
        这两次遍历的效果是叠加的，最终表现为扩展运算符遍历函数genFuncWithReturn返回的遍历器对象。
        所以，最后的数据表达式得到的值等于[ 'a', 'b' ]。
        但是，函数genFuncWithReturn的return语句的返回值The result，会返回给函数logReturned内部的result变量，因此会有终端输出*/

        /*yield*命令可以很方便地取出嵌套数组的所有成员*/
        function* iterTree(tree) {
            if (Array.isArray(tree)) {
                for (let i = 0; i < tree.length; i++) {
                    yield* iterTree(tree[i]);
                }
            } else {
                yield tree;
            }
        }

        const tree = ['a', ['b', 'c'], ['d', 'e']];
        for (const x of iterTree(tree)) {
            console.log(x); // a b c d e
        }

        /*下面是一个稍微复杂的例子，使用yield*语句遍历完全二叉树*/

        /*下面是二叉树的构造函数，三个参数分别是左树、当前节点和右树*/
        function Tree(left, label, right) {
            this.left = left;
            this.label = label;
            this.right = right;
        }

        /*下面是中序(inorder)遍历函数，由于返回的是一个遍历器，所以要用generator函数，函数体内采用递归算法，所以左树和右树要用yield*遍历*/
        function* inorder(t) {
            if (t) {
                yield* inorder(t.left);
                yield t.label;
                yield* inorder(t.right);
            }
        }

        /*下面生成二叉树*/
        function make(array) {
            // 判断是否为叶节点
            if (array.length === 1) return new Tree(null, array[0], null);
            return new Tree(make(array[0]), array[1], make(array[2]));
        }

        let binaryTree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
        /*遍历二叉树*/
        const result = [];
        for (let node of inorder(binaryTree)) {
            result.push(node);
        }
        console.log(result); // ["a", "b", "c", "d", "e", "f", "g"]
    }
    {
        /**
         * 作为对象属性的Generator函数
         */
        /*如果一个对象的属性是Generator函数，可以简写成下面的形式*/
        let obj = {
            * myGeneratorMethod() {
            }
        };
        /*上面代码中，myGeneratorMethod属性前面有一个星号，表示这个属性是一个Generator函数*/

        /*它的完整形式如下，与上面的写法是等价的。*/
        /*let obj = {
            myGeneratorMethod: function* () {
                // ···
            }
        };*/
    }
    {
        /**
         * Generator函数的this
         * Generator函数总是返回一个遍历器，ES6规定这个遍历器是Generator函数的实例，也继承了Generator函数的prototype对象上的方法。
         */
        function* g() {
        }

        g.prototype.hello = function () {
            return 'hi!';
        };
        let obj = g();
        console.log(obj instanceof g); // true
        console.log(obj.hello()); // hi!
        /*上面代码表明，Generator函数g返回的遍历器obj，是g的实例，而且继承了g.prototype。但是，如果把g当作普通的构造函数，并不会生效，因为g返回的总是遍历器对象，而不是this对象*/

        function* gen() {
            this.a = 11;
        }

        let object = gen();
        console.log(object.a); // undefined
        /*上面代码中，Generator函数gen在this对象上面添加了一个属性a，但是object对象拿不到这个属性*/

        /*Generator函数也不能跟new命令一起用，会报错*/
        function* F() {
            yield this.x = 2;
            yield this.y = 3;
        }

        // console.log(new F()); // Uncaught TypeError: F is not a constructor
        /*上面代码中，new命令跟构造函数F一起使用，结果报错，因为F不是构造函数*/

        /*那么，有没有办法让Generator函数返回一个正常的对象实例，既可以用next方法，又可以获得正常的this？*/

        /*下面是一个变通方法。首先，生成一个空对象，使用call方法绑定Generator函数内部的this。这样，构造函数调用以后，这个空对象就是Generator函数的实例对象了*/
        function* Fn() {
            this.a = 1;
            yield this.b = 2;
            yield this.c = 3;
        }

        const o = {};
        const f = Fn.call(o);
        console.log(f.next()); // {value: 2, done: false}
        console.log(f.next()); // {value: 3, done: false}
        console.log(f.next()); // {value: undefined, done: true}
        console.log(o.a); // 1
        console.log(o.b); // 2
        console.log(o.c); // 3
        /*上面代码中，首先是F内部的this对象绑定obj对象，然后调用它，返回一个Iterator对象。
        这个对象执行三次next方法(因为F内部有两个yield语句)，完成F内部所有代码的运行。
        这时，所有内部属性都绑定在obj对象上了，因此obj对象也就成了F的实例*/
        /*上面代码中，执行的是遍历器对象f，但是生成的对象实例是obj，有没有办法将这两个对象统一呢？*/
        /*一个办法就是将obj换成F.prototype*/

        let fn = Fn.call(Fn.prototype);
        console.log(fn.next()); // {value: 2, done: false}
        console.log(fn.next()); // {value: 3, done: false}
        console.log(fn.next()); // {value: undefined, done: true}
        console.log(fn.a); // 1
        console.log(fn.b); // 2
        console.log(fn.c); // 3

        /*再将Fn改成构造函数，就可以对它执行new命令了*/
        function fun() {
            return Fn.call(Fn.prototype);
        }

        let func = new fun();
        console.log(func.next()); // {value: 2, done: false}
        console.log(func.next()); // {value: 3, done: false}
        console.log(func.next()); // {value: undefined, done: true}
        console.log(func.a); // 1
        console.log(func.b); // 2
        console.log(func.c); // 3
    }
    {
        /**Generator与状态机*/
        /*下面的clock函数就是一个状态机*/
        let ticking = true;
        const clock = function () {
            if (ticking)
                console.log('Tick!');
            else
                console.log('Tock!');
            ticking = !ticking;
        };
        console.log(clock()); // Tick!
        /*上面代码的clock函数一共有两种状态(Tick和Tock)，每运行一次，就改变一次状态。这个函数如果用Generator实现，就是下面这样*/
        /*const clock = function* () {
            while (true) {
                console.log('Tick!');
                yield;
                console.log('Tock!');
                yield;
            }
        };*/
    }
    {
        /**
         * Generator与协程
         * 协程(coroutine)是一种程序运行的方式，可以理解成“协作的线程”或“协作的函数”。协程既可以用单线程实现，也可以用多线程实现。前者是一种特殊的子例程，后者是一种特殊的线程
         */
        /*1、协程与子例程的差异
        * 传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式，只有当调用的子函数完全执行完毕，才会结束执行父函数。
        * 协程与其不同，多个线程(单线程情况下，即多个函数)可以并行执行，但是只有一个线程(或函数)处于正在运行的状态，
        * 其他线程(或函数)都处于暂停态(suspended)，线程(或函数)之间可以交换执行权。
        * 也就是说，一个线程(或函数)执行到一半，可以暂停执行，将执行权交给另一个线程(或函数)，等到稍后收回执行权的时候，再恢复执行。
        * 这种可以并行执行、交换执行权的线程(或函数)，就称为协程
        * 从实现上看，在内存中，子例程只使用一个栈(stack)，而协程是同时存在多个栈，但只有一个栈是在运行状态，也就是说，协程是以多占用内存为代价，实现多任务的并行*/

        /*2、协程与普通线程的差异
        * 不难看出，协程适合用于多任务运行的环境。
        * 在这个意义上，它与普通的线程很相似，都有自己的执行上下文、可以分享全局变量。
        * 它们的不同之处在于，同一时间可以有多个线程处于运行状态，但是运行的协程只能有一个，其他协程都处于暂停状态。
        * 此外，普通的线程是抢先式的，到底哪个线程优先得到资源，必须由运行环境决定，但是协程是合作式的，执行权由协程自己分配*/

        /*异步操作的同步化表达
        * Generator函数的暂停执行的效果，意味着可以把异步操作写在yield语句里面，等到调用next方法时再往后执行。
        * 这实际上等同于不需要写回调函数了，因为异步操作的后续操作可以放在yield语句下面，反正要等到调用next方法时再执行。
        * 所以，Generator函数的一个重要实际意义就是用来处理异步操作，改写回调函数。*/
        /*function* loadUI() {
            showLoadingScreen();
            yield loadUIDataAsynchronously();
            hideLoadingScreen();
        }

        const loader = loadUI();
        // 加载UI
        loader.next()
        // 卸载UI
        loader.next()*/

        /*Ajax是典型的异步操作，通过Generator函数部署Ajax操作，可以用同步的方式表达*/
        function* main() {
            const result = yield request("http://some.url");
            const resp = JSON.parse(result);
            console.log(resp.value);
        }

        function request(url) {
            function makeAjaxCall(url, resp) {
                console.log(url);
            }

            makeAjaxCall(url, function (response) {
                it.next(response);
            });
        }

        const it = main();
        console.log(it.next()); // http://some.url


        /*function* numbers() {
            let file = new FileReader("numbers.txt");
            try {
                while(!file.eof) {
                    yield parseInt(file.readLine(), 10);
                }
            } finally {
                file.close();
            }
        }*/

    }
    {
        /**
         * 部署Iterator接口
         * 利用Generator函数，可以在任意对象上部署Iterator接口
         */
        function* iterEntries(obj) {
            let keys = Object.keys(obj);
            for (let i = 0; i < keys.length; i++) {
                let key = keys[i];
                yield [key, obj[key]];
            }
        }

        let myObj = {foo: 3, bar: 7};
        for (const [key, value] of iterEntries(myObj)) {
            console.log(key, value); // foo 3  bar 7
        }
        /*上述代码中，myObj是一个普通对象，通过iterEntries函数，就有了Iterator接口。也就是说，可以在任意对象上部署next方法*/

        /*下面是一个对数组部署Iterator接口的例子，尽管数组原生具有这个接口*/
        function* makeSimpleGenerator(array) {
            let nextIndex = 0;

            while (nextIndex < array.length) {
                yield array[nextIndex++];
            }
        }

        let gen = makeSimpleGenerator(['yo', 'ya']);
        console.log(gen.next().value); // yo
        console.log(gen.next().value); // ya
        console.log(gen.next().done); // true
    }
</script>
</body>
</html>